/* *****************************************************************************
 * JFab (http://code.google.com/p/jfab)
 * Copyright (c) 2011 JFab.org
 * Admin jfab@jeffreythompson.net
 *
 * See the file "LICENSE.txt" for information on usage and redistribution of
 * this file, and for a DISCLAIMER OF ALL WARRANTIES.
 * *****************************************************************************/

package org.jfab.core.analyzer;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.jfab.core.collection.CGAnnotationList;
import org.jfab.core.collection.CGConstructorList;
import org.jfab.core.collection.CGEnumItemList;
import org.jfab.core.collection.CGFieldList;
import org.jfab.core.collection.CGInitializationBlockList;
import org.jfab.core.collection.CGMethodList;
import org.jfab.core.collection.CGTypeList;
import org.jfab.core.collection.CommentList;
import org.jfab.core.collection.ModifierList;
import org.jfab.core.factory.CGAnnotationFactory;
import org.jfab.core.factory.CGEnumFactory;
import org.jfab.core.factory.CGEnumItemFactory;
import org.jfab.core.factory.CGTypeFactory;
import org.jfab.core.factory.StringFactory;
import org.jfab.core.model.CGConstructor;
import org.jfab.core.model.CGEnum;
import org.jfab.core.model.CGEnumItem;
import org.jfab.core.model.CGTypeUtilities;
import org.jfab.core.model.TypeFactory;

/**
 * Provides a enum analyzer.
 */
public final class EnumAnalyzer extends
        AbstractCompilationUnitAnalyzer<CGEnum, Class<?>>
{
    /** List of fields to ignore. */
    private static final List<String> IGNORE_FIELDS;

    static
    {
        final List<String> list = new ArrayList<String>();
        list.add("ENUM$VALUES");
        list.add("$VALUES");

        IGNORE_FIELDS = list;
    }

    /** List of methods to ignore. */
    private static final List<String> IGNORE_METHODS;

    static
    {
        final List<String> list = new ArrayList<String>();
        list.add("valueOf");
        list.add("values");

        IGNORE_METHODS = list;
    }

    /** Constructor analyzer. */
    private final ConstructorAnalyzer _constructorAnalyzer;

    /** Enum factory. */
    private final CGEnumFactory _enumFactory;

    /** Enum item factory. */
    private final CGEnumItemFactory _enumItemFactory;

    /** Field analyzer. */
    private final FieldAnalyzer _fieldAnalyzer;

    /** Method analyzer. */
    private final MethodAnalyzer _methodAnalyzer;

    /**
     * Construct this object with the given parameters.
     * 
     * @param typeFactory Type factory.
     * @param cgTypeUtils Code generator type utilities. (required)
     * @param cgTypeFactory Code generator type factory.
     * @param stringFactory String factory.
     * @param annotationFactory Annotation factory.
     * @param comparator Comparator.
     * @param enumItemFactory Enum item factory.
     * @param enumFactory Enum factory.
     * @param constructorAnalyzer Constructor analyzer.
     * @param fieldAnalyzer Field analyzer.
     * @param methodAnalyzer Method analyzer.
     */
    public EnumAnalyzer(final TypeFactory typeFactory,
            final CGTypeUtilities cgTypeUtils,
            final CGTypeFactory cgTypeFactory,
            final StringFactory stringFactory,
            final CGAnnotationFactory annotationFactory,
            final Comparator<CGEnum> comparator,
            final CGEnumItemFactory enumItemFactory,
            final CGEnumFactory enumFactory, final FieldAnalyzer fieldAnalyzer,
            final ConstructorAnalyzer constructorAnalyzer,
            final MethodAnalyzer methodAnalyzer)
    {
        super(typeFactory, cgTypeUtils, cgTypeFactory, stringFactory,
                annotationFactory, comparator);

        _enumItemFactory = enumItemFactory;
        _enumFactory = enumFactory;
        _fieldAnalyzer = fieldAnalyzer;
        _constructorAnalyzer = constructorAnalyzer;
        _methodAnalyzer = methodAnalyzer;
    }

    @Override
    public final CGEnum analyze(final Class<?> aClass, final String javaSource)
    {
        Validate.notNull(aClass, "aClass is null");

        CGEnum answer = null;

        final CGTypeList referencedTypes = null;
        final String packageName = aClass.getPackage().getName();
        final CommentList commentLines = parseCommentLines(javaSource);
        final CGAnnotationList annotations = null;
        final ModifierList modifiers = filterModifiers(aClass.getModifiers());
        final String enumName = aClass.getSimpleName();
        final List<String> typeParameters = null;
        final CGEnumItemList enumItems = analyzeEnumItems(
                filterEnumItems(aClass.getDeclaredFields()), javaSource);
        final int parameterOffset = 2;
        final CGConstructorList constructors = getConstructorAnalyzer()
                .analyze(aClass.getDeclaredConstructors(), javaSource,
                        parameterOffset);

        if (CollectionUtils.isNotEmpty(constructors))
        {
            for (final CGConstructor constructor : constructors)
            {
                if (constructor.getParameters().size() > 1)
                {
                    // First parameter is enum name.
                    constructor.getParameters().remove(0);
                    // Second parameter is enum ordinal.
                    constructor.getParameters().remove(0);
                }
            }
        }

        final CGInitializationBlockList initializationBlocks = null;
        final CGFieldList fields = new CGFieldList(getFieldAnalyzer().analyze(
                filterFields(aClass.getDeclaredFields()), javaSource));
        final CGMethodList methods = new CGMethodList(
                getMethodAnalyzer().analyze(
                        filterMethods(aClass.getDeclaredMethods()), javaSource));

        answer = getEnumFactory().createEnum(referencedTypes, packageName,
                commentLines, annotations, modifiers, enumName, typeParameters,
                enumItems, initializationBlocks, constructors, fields, methods);
        answer.setJavaType(getCgTypeFactory().createType(aClass));

        return answer;
    }

    /**
     * @param fields Fields.
     * @param javaSource Java source.
     * 
     * @return a list of new code generator enum items.
     */
    private CGEnumItemList analyzeEnumItems(final Field[] fields,
            final String javaSource)
    {
        final CGEnumItemList answer = new CGEnumItemList();
        final CGEnumItemFactory factory = getEnumItemFactory();

        for (final Field field : fields)
        {
            final String enumName = field.getName();

            final CommentList commentLines = parseCommentLines(javaSource,
                    enumName);

            String parameters = null;

            if (javaSource != null)
            {
                parameters = parseEnumItemParameters(javaSource, enumName);
            }

            final CGEnumItem enumItem = factory.createEnumItem(commentLines,
                    enumName, parameters);
            answer.add(enumItem);
        }

        return answer;
    }

    /**
     * @param fields Fields.
     * 
     * @return a filtered array of enum item fields.
     */
    private Field[] filterEnumItems(final Field[] fields)
    {
        final List<Field> list = new ArrayList<Field>();

        for (final Field field : fields)
        {
            if (!IGNORE_FIELDS.contains(field.getName())
                    && (field.getType() == field.getDeclaringClass()))
            {
                list.add(field);
            }
        }

        return list.toArray(new Field[list.size()]);
    }

    /**
     * @param fields Fields.
     * 
     * @return a filtered array of fields.
     */
    private Field[] filterFields(final Field[] fields)
    {
        final List<Field> list = new ArrayList<Field>();

        for (final Field field : fields)
        {
            if (!(IGNORE_FIELDS.contains(field.getName()) || (field.getType() == field
                    .getDeclaringClass())))
            {
                list.add(field);
            }
        }

        return list.toArray(new Field[list.size()]);
    }

    /**
     * @param methods Methods.
     * 
     * @return a filtered array of methods.
     */
    private Method[] filterMethods(final Method[] methods)
    {
        final List<Method> list = new ArrayList<Method>();

        for (final Method method : methods)
        {
            if (!IGNORE_METHODS.contains(method.getName()))
            {
                list.add(method);
            }
        }

        return list.toArray(new Method[list.size()]);
    }

    /**
     * @param modifiers Modifiers.
     * 
     * @return the given parameter filtered for particular modifiers.
     */
    private ModifierList filterModifiers(final int modifiers)
    {
        int value = modifiers;

        if (Modifier.isFinal(value))
        {
            value -= Modifier.FINAL;
        }

        return new ModifierList(value);
    }

    /**
     * @return the constructorAnalyzer
     */
    private ConstructorAnalyzer getConstructorAnalyzer()
    {
        return _constructorAnalyzer;
    }

    /**
     * @return the enumFactory
     */
    private CGEnumFactory getEnumFactory()
    {
        return _enumFactory;
    }

    /**
     * @return the enumItemFactory
     */
    private CGEnumItemFactory getEnumItemFactory()
    {
        return _enumItemFactory;
    }

    /**
     * @return the fieldAnalyzer
     */
    private FieldAnalyzer getFieldAnalyzer()
    {
        return _fieldAnalyzer;
    }

    /**
     * @return the methodAnalyzer
     */
    private MethodAnalyzer getMethodAnalyzer()
    {
        return _methodAnalyzer;
    }

    /**
     * @param javaSource Java source.
     * @param enumName Enum name.
     * 
     * @return enum item parameters.
     */
    private CommentList parseCommentLines(final String javaSource,
            final String enumName)
    {
        CommentList answer = null;

        int index0 = javaSource.indexOf(enumName);

        if (index0 >= 0)
        {
            String text = javaSource.substring(0, index0);
            index0 = text.lastIndexOf("/**");

            if (index0 >= 0)
            {
                text = text.substring(index0);

                if (!text.contains(","))
                {
                    text = StringUtils.substringBetween(text, "/**", "*/");

                    if (StringUtils.isNotEmpty(text))
                    {
                        final String[] lines = text.split("\n");
                        answer = new CommentList();

                        for (int i = 0; i < lines.length; i++)
                        {
                            answer.add(lines[i].trim());
                        }
                    }
                }
            }
        }

        return answer;
    }

    /**
     * @param javaSource Java source.
     * @param enumName Enum name.
     * 
     * @return enum item parameters.
     */
    private String parseEnumItemParameters(final String javaSource,
            final String enumName)
    {
        String answer = null;

        int index0 = javaSource.indexOf(enumName);

        if (index0 >= 0)
        {
            index0 += enumName.length();

            int index1 = javaSource.indexOf(',', index0);

            if (index1 < 0)
            {
                index1 = javaSource.indexOf(';', index0);
            }

            if (index1 >= 0)
            {
                String text = javaSource.substring(index0, index1);
                text = StringUtils.substringBetween(text, "(", ")");
                answer = text;
            }
        }

        return answer;
    }
}
